﻿using Neudesic.Pulse.Entities;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.IO;

namespace Neudesic.Pulse
{
    /// <remarks>
    /// This partial class implements strongly-typed API calls.
    /// </remarks>
    public partial class PulseClient
    {

        /// <summary>
        /// Removes notifications from server.
        /// </summary>
        /// <param name="user">The user entity.</param>
        public void ClearNotifications(Entity user)
        {            
            ClearNotificationRequest clear = new ClearNotificationRequest() { Clear = true };
            this.Post(clear, UriUtility.CombineUri(user.Url, "notifications"));
        }

        /// <summary>
        /// Creates an answer.
        /// </summary>
        /// <param name="text">The text.</param>
        /// <param name="question">The question activity.</param>
        /// <returns></returns>
        public string CreateAnswer(string text, Activity question)
        {
            CommentRequest comment = new CommentRequest() { Text = text };
            return this.Post(comment, UriUtility.CombineUri(question.Url, "answers"));
        }

        /// <summary>
        /// Creates the avatar image.
        /// </summary>
        /// <param name="attachment">The attachment.</param>
        /// <param name="entity">The entity.</param>
        public void CreateAvatar(byte[] attachment, Entity entity)
        {
            this.Post(attachment, UriUtility.CombineUri(entity.Url, "avatar"));
        }

        /// <summary>
        /// Creates the comment.
        /// </summary>
        /// <param name="text">The text.</param>
        /// <param name="activity">The activity.</param>
        /// <returns></returns>
        public string CreateComment(string text, Activity activity)
        {
            CommentRequest comment = new CommentRequest() { Text = text };
            return this.Post(comment, UriUtility.CombineUri(activity.Url, "comments"));
        }

        /// <summary>
        /// Creates/removes the like.
        /// </summary>
        /// <param name="isLiked">The is liked.</param>
        /// <param name="activity">The activity for which the like is submitted.</param>
        public void CreateLike(Activity activity, bool isLiked = true)
        {
            LikeRequest like = new LikeRequest() { IsLiked = isLiked };
            this.Post(like, UriUtility.CombineUri(activity.Url, "likes"));
        }

        /// <summary>
        /// Creates a page.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="description">The description.</param>
        /// <returns></returns>
        public string CreatePage(string name, string description /* TODO: Security, Browseable */)
        {
            Entity page = new Entity() { Name = name, Description = description };
            return this.Post(page, UriUtility.CombineUri(BaseAddress, "pages"));
        }

        /// <summary>
        /// Creates the phrase tag subscription.
        /// </summary>
        /// <param name="phrase">The phrase.</param>
        /// <param name="tag">The tag.</param>
        /// <param name="enabled">The enabled.</param>
        public void CreatePhraseTagSubscription(string phrase, string tag, bool enabled = true)
        {
            SubscriptionRequest subscribe = new SubscriptionRequest() { Tag = tag, IsSubscribed = enabled };
            this.Post(subscribe, UriUtility.CombineUri(BaseAddress, string.Format("/search/phrases/{0}/tags", phrase)));
        }

        /// <summary>
        /// Creates a question.
        /// </summary>
        /// <param name="question">The question.</param>
        /// <param name="entity">The entity.</param>
        /// <returns></returns>
        public string CreateQuestion(StatusRequest question, Entity entity)
        {
            // Ensure that the status has a correct type.
            question.Type = EntityType.Question.ToString();
            return this.Post(question, entity.Feed.Url);
        }

        /// <summary>
        /// Creates the status with an attached image or file.
        /// </summary>
        /// <param name="status">The status.</param>
        /// <param name="entity">The entity.</param>
        /// <param name="attachment">The attachment.</param>
        /// <param name="attachmentType">Type of the attachment.</param>
        /// <returns></returns>
        public string CreateStatus(StatusRequest status, Entity entity, byte[] attachment, AttachmentType attachmentType)
        {
            // Upload the image first, hydrate the link property, then upload the status
            string image = string.Empty;
            string streamUri = UriUtility.GetParentPath(entity.Feed.Url);

            switch (attachmentType)
            {
                case AttachmentType.File:
                    image = this.Post(attachment, UriUtility.CombineUri(streamUri, "images"));
                    break;
                case AttachmentType.Image:
                    image = this.Post(attachment, UriUtility.CombineUri(streamUri, "files"));
                    break;
                default:
                    throw new NotSupportedException(string.Format("{0} is an unknown attachment type.", attachmentType));
            }

            status.Link = image;

            return this.Post(attachment, entity.Feed.Url);
        }

        /// <summary>
        /// Creates a status.
        /// </summary>
        /// <param name="status">The status.</param>
        /// <param name="entity">The entity that submits the status.</param>
        /// <returns></returns>
        public string CreateStatus(StatusRequest status, Entity entity)
        {
            return this.Post(status, entity.Feed.Url);
        }

        /// <summary>
        /// Creates a subscription.
        /// </summary>
        /// <param name="entityToSubscribe">The entity to subscribe.</param>
        /// <param name="entity">The entity.</param>
        /// <param name="enabled">The enabled.</param>
        public void CreateSubscription(Entity entityToSubscribe, Entity entity, bool enabled = true)
        {
            SubscriptionRequest subscribe = new SubscriptionRequest() { Url = entityToSubscribe.Feed.Url, IsSubscribed = enabled };
            this.Post(subscribe, UriUtility.CombineUri(entity.Url, "subscriptions"));
        }

        /// <summary>
        /// Creates a tag.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <returns></returns>
        public string CreateTag(string name)
        {
            Entity system = new Entity() { Name = name };
            return this.Post(system, UriUtility.CombineUri(BaseAddress, "tags"));
        }

        /// <summary>
        /// Creates the tag subscription.
        /// </summary>
        /// <param name="tag">The tag.</param>
        /// <param name="entityToSubscribe">The entity to subscribe.</param>
        /// <param name="enabled">The enabled.</param>
        public void CreateTagSubscription(string tag, Entity entityToSubscribe, bool enabled = true)
        {
            Regex regex = new Regex(@"streams/(?<streamid>(\{){0,1}[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}(\}){0,1})");
            Match match = regex.Match(entityToSubscribe.Url);
            if (match != null && match.Success)
            {
                string postUri = UriUtility.CombineUri(BaseAddress, string.Format("{0}/tags", match.Groups["streamid"].Value));
                SubscriptionRequest subscribe = new SubscriptionRequest() { IsSubscribed = enabled, Tag = tag };
                this.Post(subscribe, postUri);
                return;
            }

            throw new ArgumentException("Entity stream uri not in a recognizable format.  Uri should be ~/{streamid}", "entityToSubscribe");
        }

        /// <summary>
        /// Creates the tag activity subscription.
        /// </summary>
        /// <param name="tag">The tag.</param>
        /// <param name="activityToSubscribe">The activity for which the subscription should be created.</param>
        /// <param name="enabled">The enabled.</param>
        public void CreateTagActivitySubscription(string tag, Activity activityToSubscribe, bool enabled = true)
        {
            SubscriptionRequest subscribe = new SubscriptionRequest() { IsSubscribed = enabled, Tag = tag };
            this.Post(subscribe, UriUtility.CombineUri(activityToSubscribe.Url, "tags"));
        }

        /// <summary>
        /// Creates a system.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="description">The description.</param>
        /// <returns></returns>
        public string CreateSystem(string name, string description /* TODO: Security */)
        {
            Entity system = new Entity() { Name = name, Description = description };
            return this.Post(system, UriUtility.CombineUri(BaseAddress, "systems"));
        }

        /// <summary>
        /// Casts a vote for an answer.
        /// </summary>
        /// <param name="voteRequestType">Type of the vote request.</param>
        /// <param name="answer">The answer activity.</param>
        public void CreateVote(VoteRequestType voteRequestType, Activity answer)
        {
            VoteRequest vote = new VoteRequest() { Vote = voteRequestType.GetDescription() };
            this.Post(vote, UriUtility.CombineUri(answer.Url, "votes"));
        }

        /// <summary>
        /// Deletes the phrase tag subscription.
        /// </summary>
        /// <param name="phrase">The phrase.</param>
        /// <param name="tag">The tag.</param>
        public void DeletePhraseTagSubscription(string phrase, string tag)
        {
            this.CreatePhraseTagSubscription(phrase, tag, false);
        }
    
        /// <summary>
        /// Deletes the resource.
        /// </summary>
        /// <param name="uri">The URI.</param>
        public void DeleteResource(string uri)
        {
            this.Delete(uri);
        }

        /// <summary>
        /// Deletes the resource.
        /// </summary>
        /// <param name="entity">The entity.</param>
        public void DeleteResource(Entity entity)
        {
            this.Delete(entity.Url);
        }

        /// <summary>
        /// Deletes the resource.
        /// </summary>
        /// <param name="activity">The activity.</param>
        public void DeleteResource(Activity activity)
        {
            this.Delete(activity.Url);
        }

        /// <summary>
        /// Deletes a subscription.
        /// </summary>
        /// <param name="entityToSubscribe">The entity to subscribe.</param>
        /// <param name="userUri">The user URI.</param>
        public void DeleteSubscription(Entity entityToSubscribe, Entity entity)
        {
            this.CreateSubscription(entityToSubscribe, entity, false);
        }

        /// <summary>
        /// Deletes the tag subscription.
        /// </summary>
        /// <param name="tag">The tag.</param>
        /// <param name="entityToSubscribe">The entity to subscribe.</param>
        public void DeleteTagSubscription(string tag, Entity entityToSubscribe)
        {
            this.CreateTagSubscription(tag, entityToSubscribe, false);
        }

        /// <summary>
        /// Deletes the tag activity subscription.
        /// </summary>
        /// <param name="tag">The tag.</param>
        /// <param name="entityToSubscribe">The entity to subscribe.</param>
        public void DeleteTagActivitySubscription(string tag, Activity activityToSubscribe)
        {
            this.CreateTagActivitySubscription(tag, activityToSubscribe, false);
        }

        /// <summary>
        /// Gets an activity.
        /// </summary>
        /// <remarks>
        /// The uri will vary based on the type of activity.
        /// </remarks>
        /// <param name="uri">The URI.</param>
        /// <returns></returns>
        public Activity GetActivity(string uri)
        {
            return this.Get<Activity>(uri, "activity");
        }

        /// <summary>
        /// Gets the handle.
        /// </summary>
        /// <param name="user">The user.</param>
        /// <returns></returns>
        public string GetHandle(Entity user)
        {
            var response = this.Get(UriUtility.CombineUri(user.Url, "handle"));

            // Translate the response into json content and infer the count
            JObject jLinq = JObject.Parse(new StreamReader( response.GetResponseStream() ).ReadToEnd());
            JToken count = jLinq.SelectToken("handle");

            return count.Value<string>();
        }

        /// <summary>
        /// Gets a paged activity list.
        /// </summary>
        /// <remarks>
        /// The uri will vary depending on the entity type.
        /// </remarks>
        /// <param name="uri">The URI.</param>
        /// <returns></returns>
        public PagedActivityList GetPagedActivityList(string uri, string view = null)
        {
            if (view == null)
            {
                return this.Get<PagedActivityList>(UriUtility.CombineUri(uri, "activities"));
            }
            else
            {
                return this.Get<PagedActivityList>(UriUtility.CombineUri(uri, "activities?view="+view));
            }
        }

        /// <summary>
        /// Gets the paged activity list.
        /// </summary>
        /// <param name="entity">The entity.</param>
        /// <returns></returns>
        public PagedActivityList GetPagedActivityList(Entity entity, string view = null)
        {
            return this.GetPagedActivityList(entity.Url, view);
        }

        /// <summary>
        /// Gets the count of activities waiting to be retrieved for the user.
        /// </summary>
        /// <param name="userUri">The user URI.</param>
        /// <returns></returns>
        public int GetPendingActivityCount(string newerActivityUri)
        {

            Uri activityUri = new Uri(newerActivityUri);
            string uri = UriUtility.CombineUri(activityUri.GetLeftPart(UriPartial.Path), "pending") + activityUri.Query;

            var response = this.Get(uri);

            // Translate the response into json content and infer the count
            JObject jLinq = JObject.Parse(new StreamReader(response.GetResponseStream()).ReadToEnd() );
            JToken count = jLinq.SelectToken("count");

            return count.Value<int>();

        }

        /// <summary>
        /// Gets a list of question suggestions based on the input.
        /// </summary>
        /// <param name="phrase">The phrase.</param>
        /// <returns></returns>
        public List<Activity> GetQuestionSuggestions(string phrase)
        {
            return this.Get<List<Activity>>(UriUtility.CombineUri(BaseAddress, string.Format("questions/suggestions?p={0}", phrase)), "questions");
        }

        /// <summary>
        /// Gets a list of entity subscriptions.
        /// </summary>
        /// <param name="user">The user.</param>
        /// <returns></returns>
        public List<Entity> GetSubscriptions(Entity user)
        {
                return this.Get<List<Entity>>(UriUtility.CombineUri(user.Url, "subscriptions"), "streams");
        }

        /// <summary>
        /// Gets a list of entity subscriptions filtered by type.
        /// </summary>
        /// <param name="user">The user.</param>
        /// <param name="type">The type.</param>
        /// <returns></returns>
        public List<Entity> GetSubscriptions(Entity user, EntityType type)
        {
                return this.Get<List<Entity>>(UriUtility.CombineUri(user.Url, string.Format("subscriptions?type={0}", type.GetSearchType())), "streams");
        }

        /// <summary>
        /// Searches for given type of entity using a specified phrase.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <param name="text">The text.</param>
        /// <returns></returns>
        public List<T> Search<T>(EntityType type, string phrase)
        {
            return this.Get<List<T>>(UriUtility.CombineUri(BaseAddress, string.Format("search/{0}?p={1}", type.GetSearchTerm(), phrase)), type.GetSearchResultNodeName());
        }

        /// <summary>
        /// Updates a page.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="description">The description.</param>
        /// <param name="streamUri">The stream URI in the format ~/streams/{page-id}.</param>
        /// <returns></returns>
        public string UpdatePage(string name, string description /* TODO: Security, Browseable */, string streamUri)
        {
            Regex regex = new Regex(@"streams/(?<pageid>(\{){0,1}[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}(\}){0,1})");
            Match match = regex.Match(streamUri);
            if (match != null && match.Success)
            {
                string pageUri = UriUtility.CombineUri(BaseAddress, string.Format("pages/{0}", match.Groups["pageid"].Value));
                Entity page = new Entity() { Name = name, Description = description };
                return this.Post(page, pageUri);
            }

            throw new ArgumentException("Stream uri not in a recognizable format.  Uri should be ~/streams/{page-id}", "streamUri");

        }

        /// <summary>
        /// Updates a system.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="description">The description.</param>
        /// <param name="streamUri">The stream URI in the format ~/streams/{system-id}.</param>
        /// <returns></returns>
        public string UpdateSystem(string name, string description /* TODO: Security */, string streamUri)
        {
            Regex regex = new Regex(@"streams/(?<systemid>(\{){0,1}[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}(\}){0,1})");
            Match match = regex.Match(streamUri);
            if (match != null && match.Success)
            {
                string systemUri = UriUtility.CombineUri(BaseAddress, string.Format("systems/{0}", match.Groups["systemid"].Value));
                Entity system = new Entity() { Name = name, Description = description };
                return this.Post(system, systemUri);
            }

            throw new ArgumentException("Stream uri not in a recognizable format.  Uri should be ~/streams/{system-id}", "streamUri");
        }

        /// <summary>
        /// Updates a tag.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="tagUri">The tag URI.</param>
        /// <returns></returns>
        public string UpdateTag(string name, string tagUri)
        {
            Entity tag = new Entity() { Name = name };
            return this.Post(tag, tagUri);
        }

    }
}
